package mobile.game.common.net;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;

import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import mobile.game.core.Chunk;
import mobile.game.core.Port;
import mobile.game.core.PortTask;
import mobile.game.core.Service;
import mobile.game.core.gen.proxy.DistrClass;
import mobile.game.core.gen.proxy.DistrMethod;
import mobile.game.core.support.ConnectionStatus;
import mobile.game.core.support.Param;
import mobile.game.core.support.SysException;
import mobile.game.core.support.TickTimer;
import mobile.game.core.support.Utils;
import mobile.game.core.support.log.LogCore;

@DistrClass(importClass={List.class, Chunk.class, ConnectionStatus.class})
public class Connection extends Service {
	// 连接id
	private long id;
	// 清理中
	private boolean clearing = false;
	// 连接channel
	private final Channel channel;
	// 连接状态
	private ConnectionStatus status = new ConnectionStatus();
	// 待处理数据
	private final LinkedBlockingQueue<byte[]> input = new LinkedBlockingQueue<>();
	
	// 延迟清理定时器
	private TickTimer timerDelayClear = new TickTimer();
	
	// 断开连接时是否立即清理数据
	private boolean closeImmediate = false;
	
	/* 保证短时间内收到的包数不至于太夸张 */
	// COUNTTIME时间内收到多于COUNTMAX数量的包
	private static int COUNTMAX = 300;
	private static long COUNTTIME = 60000;
	private int countBuff = 0;
	private long countBuffStartTime = 0L;
	
	/* 连接验证 */
	// 连续三次检测不返回，算超时
	public static long CONN_CHECK_TIMES = 3;
	// 每5秒检查一次连接是否超时
	public TickTimer connCheckTickTimer = new TickTimer(5 * 60000);
	// 连续检查连续未收到返回的次数
	public int connCheckCounter = 0;
	
	static {
		// windows下认为是开发环境，避免debug造成的超时断开连接，这里加大检查次数
		if(Utils.isWin()) {
			CONN_CHECK_TIMES = Long.MAX_VALUE;
		}
	}
	
	public Connection(Channel channel, long id, String servId) {
		super(servId);
		this.id = id;
		this.channel = channel;
	}

	/* 返回连接id
	 * @see org.acoral.core.Service#getId()
	 */
	@Override
	public Object getId() {
		return id;
	}

	@Override
	public void pulseOverride() {
		long now = port.getTimeCurrent();
		
		/* 1分钟内收到超过300个包，可能是被攻击了 */
		if(countBuff == 0) {
			countBuffStartTime = now;
		}
		countBuff += input.size();
		if(countBuff > COUNTMAX) {
			if(now - countBuffStartTime < COUNTTIME) {
				LogCore.conn.warn("连接协议请求数量异常，短时间内收到过多的包，count={}, time={}", 
						countBuff, now - countBuffStartTime);
			}
			countBuff = 0;
		}
		
		// 处理接收到的数据
		pulseInput();
		
		// 连接验证
		connCheck();
		
		// 延迟清理已到，开始清理
		if(timerDelayClear.isOnce(now)) {
			// 进行关闭处理
			clear();
		}
	}

	@Override
	public String toString() {
		return status.toString();
	}

	/**
	 * 处理接收到的数据
	 */
	private void pulseInput() {
		while(!input.isEmpty()) {
			inputHandler(input.poll());
		}
	}
	
	/**
	 * 立即清理玩家数据
	 */
	private void clear() {
		if(clearing) {
			return;
		}
		
		// 设置为清理状态
		clearing = true;
		
		// 日志
		LogCore.conn.debug("进行连接关闭的清理工作：connId={}, status={}", id, status);
		
		// 根据登陆阶段，通知游戏服务器进行处理
		switch(status.step) {
		// 登陆阶段
		case ConnectionStatus.STATUS_LOGIN:
		case ConnectionStatus.STATUS_GATE:
			{
				
			}
			break;
		
		// 游戏阶段
		case ConnectionStatus.STATUS_PLAYING:
			{
				
			}
			break;
		
		// 关闭阶段
		case ConnectionStatus.STATUS_CLOSED:
			break;
		}
		
		// 删除连接服务
		port.delServiceBySafe(getId());
	}
	
	/**
	 * channel收到数据
	 * @param data
	 */
	public void onChannelDataComing(byte[] data) {
		try {
			input.put(data);
		} catch(InterruptedException e) {
			throw new SysException(e);
		}
	}
	
	/**
	 * channel连接断开
	 */
	public void onChannelClosed() {
		// 正在清理中
		if(clearing || timerDelayClear.isStarted()) {
			return;
		}

		// 延迟关闭(等待重连)
		if(status.step == ConnectionStatus.STATUS_PLAYING && !closeImmediate) {
//			timerDelayClear.start(ServerConfig.CONN_RECONNECT_TIME * Time.SEC);
			
			// 通知HumanObject玩家已掉线，等待重连中
//			HumanObjectServiceProxy prx = HumanObjectServiceProxy.newInstance(status.stageNodeId, 
//					status.stagePortId, status.humanId);
//			prx.connDropped(id);
		// 立即关闭
		} else {
			clear();
		}
	}
	
	/**
	 * 消息分发处理
	 * @param msgbuf
	 */
	private void inputHandler(byte[] msgbuf) {
		int msgId = Utils.bytesToInt(msgbuf, 4);
		// 去掉乱七八糟的东西
		if(msgId < 0 || msgId > 99999) {
			return;
		}
		
		// 根据不同阶段决定转发目标
		switch(status.step) {
		// 登录阶段
		case ConnectionStatus.STATUS_LOGIN:
		case ConnectionStatus.STATUS_GATE:
			{
//				AccountServiceProxy prx = AccountServiceProxy.newInstance(ServerDistrConfig.NODE_DEFAULT, 
//						ServerDistrConfig.PORT_DEFAULT, ServerDistrConfig.SERV_GATE);
//				prx.msgHandler(id, status, msgbuf);
			}
			break;
		// 游戏阶段
		case ConnectionStatus.STATUS_PLAYING:
			{
//				HumanObjectServiceProxy prx = HumanObjectServiceProxy.newInstance(status.stageNodeId, 
//						status.stagePortId, status.humanId);
//				prx.msgHandler(id, msgbuf);
			}
			break;
		// 关闭阶段
		case ConnectionStatus.STATUS_CLOSED:
			LogCore.conn.debug("接到客户端消息，但玩家已是断线状态，忽略此消息");
			break;
		}
	}
	
	/**
	 * 连接验证，检查不正常情况：Connection还在，而对应的AccountObject或者HumanObject却已销毁
	 */
	private void connCheck() {
		// 验证间隔时间是否到了
		if(!connCheckTickTimer.isPeriod(Port.getTime())) {
			return;
		}
		
		// 避免由于Debug断点等情况，造成瞬间发送多个检查请求
		connCheckTickTimer.reStart();
		
		// 清理掉超时的连接，两种超时情况
		// 1.登录阶段，连续多次在AccountService未查到该连接对应的AccountObject对象
		// 2.游戏阶段，连读多次在HumanObjectService未查到该连接对象的HumanObject对象
		// 原因都是，要么玩家主动退出了，或者被KICK掉了
		if(connCheckCounter >= CONN_CHECK_TIMES) {
			// 还处于连接状态，服务器强制断开连接
			if(channel.isOpen()) {
				channel.close();
			}
			
			// 日志
			LogCore.conn.warn("清理错误的连接：conn={}, status={}, account={}, humanId={}",
					id, status.step, status.account, status.humanId);
			
			return;
		}
		
		// 根据状态进行验证
		switch(status.step) {
		case ConnectionStatus.STATUS_LOGIN:
		case ConnectionStatus.STATUS_GATE:
			{
//				AccountServiceProxy prx = AccountServiceProxy.newInstance(ServerDistrConfig.NODE_DEFAULT, 
//						ServerDistrConfig.PORT_DEFAULT, ServerDistrConfig.SERV_GATE);
//				prx.connCheck(id);
//				prx.listenResult(this::_result_pulseConnCheck);
			}
			break;
		case ConnectionStatus.STATUS_PLAYING:
			{
//				HumanObjectServiceProxy prx = HumanObjectServiceProxy.newInstance(status.stageNodeId, 
//						status.stagePortId, status.humanId);
//				prx.connCheck(id);
//				prx.listenResult(this::_result_pulseConnCheck);
			}
			break;
		}
		
		// 累加连接检查次数
		connCheckCounter++;
	}
	
	public void _result_pulseConnCheck(Param results, Param context) {
		boolean has = results.get();
		
		// 找到这个连接对应的AccountObject或HumanObject，清空累计次数
		if(has) {
			connCheckCounter = 0;
		// 没找到
		} else {
			// do nothing
		}
	}
	
	/**
	 * 发送消息至客户端，服务器发往客户端的消息不加密，客户端发往服务器的消息加密
	 * 服务器发往客户端的消息不需要加密
	 * 而客户端发往服务器的消息需要加密，这样客户端外挂没有密钥(通信密钥每次连接时由服务器随机一个)，就没法攻击服务器了
	 * @param msgId
	 * @param msgbuf
	 */
	@DistrMethod
	public void sendMsg(int msgId, Chunk msgbuf) {
		if(!channel.isActive()) {
			return;
		}
		
		if(!channel.isWritable()) {
			return;
		}
		
		// 构造头数据
		ByteBuf head = channel.alloc().buffer(8);
		head.writeInt(msgbuf.length + 8);
		head.writeInt(msgId);
		
		// 写入数据
		channel.write(head);
		// Chunk类型的msgbuf肯定是protobuf直接生成的，所以buffer属性中不会有多余的数据
		// 才能这么用，其他地方Chunk类型不建议直接使用内部的buffer
		channel.write(msgbuf.buffer);
		channel.flush();
	}
	
	@DistrMethod
	public void sendMsg(List<Integer> idList, List<Chunk> chunkList) {
		if(!channel.isActive()) {
			return;
		}
		
		if(!channel.isWritable()) {
			return;
		}
		
		for(int i = 0; i < idList.size(); ++i) {
			int msgId = idList.get(i);
			Chunk msgbuf = chunkList.get(i);
			// 构造头数据
			ByteBuf head = channel.alloc().buffer(8);
			head.writeInt(msgbuf.length + 8);
			head.writeInt(msgId);
			
			// 写入数据
			channel.write(head);
			// Chunk类型的msgbuf肯定是protobuf直接生成的，所以buffer属性中不会有多余的数据
			// 才能这么用，其他地方Chunk类型不建议直接使用内部的buffer
			channel.write(msgbuf.buffer);
		}
		
		channel.flush();
	}
	
	/**
	 * 关闭连接
	 */
	@DistrMethod
	public void closeImmediate() {
		port.addTask(new PortTask() {
			
			@Override
			public void execute(Port port) {
				// 立即关闭客户端
				closeImmediate = true;
				// 还处于连接状态，服务器强制断开连接
				if(channel.isOpen()) {
					channel.close();
				} else {
					clear();
				}
			}
		});
	}
	
	/**
	 * 更新连接状态
	 * @param status
	 */
	@DistrMethod
	public void statusUpdate(ConnectionStatus status) {
		this.status = status;
	}
	
	/**
	 * 更新连接状态
	 * @param stageNodeId
	 * @param stagePortId
	 */
	@DistrMethod
	public void statusUpdate(String stageNodeId, String stagePortId) {
		status.stageNodeId = stageNodeId;
		status.stagePortId = stagePortId;
	}
	
	/**
	 * 获取连接状态
	 * @return
	 */
	public ConnectionStatus getStatus() {
		return status;
	}
	
	/**
	 * 获取连接的ip地址
	 */
	@DistrMethod
	public void getIpAddress() {
		if(channel != null && channel.remoteAddress() != null
				&& channel.remoteAddress() instanceof InetSocketAddress) {
			InetSocketAddress socketAddress = (InetSocketAddress)channel.remoteAddress();
			InetAddress ia = socketAddress.getAddress();
			port.returns(ia.getHostAddress());
		} else {
			port.returns("");
		}
	}
	
}
